﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using Crude.Common;
using System.Reflection;
using Crude.Core.Repository;

namespace Crude.Core
{
	public class UpdateProjectObjectModel
	{
		public void Update(string projectFilePath, bool onlyAddCrudeDependencies, bool resolveToRemoteRepositoryVersion)
		{
			Project project = new Project(projectFilePath);

			ProjectObjectModel pom;
			ProjectObjectModel originalPom = null;

			if (File.Exists(project.PomFilePath))
			{
				ProjectObjectModelLoader projectObjectModelLoader = new ProjectObjectModelLoader(project.PomFilePath);

				pom = projectObjectModelLoader.Load();
				if (pom.Dependencies == null)
				{
					pom.Dependencies = new List<ProjectDependency>();
				}
                if (pom.OutputFiles == null)
                {
                    pom.OutputFiles = new List<OutputFile>();
                }            

                originalPom = projectObjectModelLoader.Load();
            }
            else
            {
                pom = new ProjectObjectModel();
            }

			pom.Name = project.AssemblyName;

            if (string.IsNullOrEmpty(pom.Description))
            {
                pom.Description = project.GetProjectDescription();
            }

			SupportedOutputType supportedOutputType;

			if (!new SupportedOutputTypeLoader().Load().TryGetValue(project.OutputType, out supportedOutputType))
			{
				supportedOutputType = new SupportedOutputType()
				{
					Extension = "dll",
					DebugFile = false
				};
			}

			// set main output file
			pom.MainOutputFile = string.Format("{0}.{1}", project.AssemblyName, supportedOutputType.Extension);
            pom.Version = project.GetProjectVersion();

			ResolveAssemblyReferences(project, pom, onlyAddCrudeDependencies, resolveToRemoteRepositoryVersion);

			ResolveProjectReferences(project, pom, onlyAddCrudeDependencies, resolveToRemoteRepositoryVersion);

			UpdateOutputFiles(project, pom, supportedOutputType.DebugFile);
			if (originalPom == null || !originalPom.Equals(pom))
			{
				new ProjectObjectModelSaver().Save(project.PomFilePath, pom);
			}
		}

		

		private void UpdateOutputFiles(Project project, ProjectObjectModel pom, bool debugFile)
		{
			if (debugFile)
			{
				string pdbName = string.Format("{0}.pdb", project.AssemblyName);

				OutputFile pdbOutputFile = (from outputFile in pom.OutputFiles
																		where outputFile.FileName.EndsWith(".pdb")
																		select outputFile).FirstOrDefault();

				if (pdbOutputFile != null)
				{
					pdbOutputFile.FileName = pdbName;
				}
				else
				{
					pdbOutputFile = new OutputFile(pdbName, OutputFileCondition.Debug);
					pom.OutputFiles.Add(pdbOutputFile);
				}
			}

			IList<string> outputFiles = project.GetOutputFiles();

			foreach (string filename in outputFiles)
			{
				OutputFile output = (from outputFile in pom.OutputFiles
														 where outputFile.FileName == filename
														 select outputFile).FirstOrDefault();

				if (output == null)
				{
					output = new OutputFile(filename, OutputFileCondition.Both);
					pom.OutputFiles.Add(output);
				}
			}
		}

		private void ResolveProjectReferences(Project project, ProjectObjectModel pom, bool onlyAddCrudeDependencies,
				 bool resolveToRemoteRepositoryVersion)
		{
			IList<ProjectReference> projectReferences = project.GetProjectReferences();

			foreach (ProjectReference reference in projectReferences)
			{
				string pomFilePath = string.Format("{0}\\{1}.pom.xml", reference.ProjectDirectory, reference.Name);

				ProjectObjectModel referencePom = null;

				if (File.Exists(pomFilePath))
				{
					referencePom = new ProjectObjectModelLoader(pomFilePath).Load();
				}

				ProjectDependency existingProjectDependency =
					 (from dependency in pom.Dependencies
						where dependency.Name == reference.Name
						select dependency).FirstOrDefault();

				if (existingProjectDependency != null)
				{
					if (referencePom != null && resolveToRemoteRepositoryVersion)
					{
						existingProjectDependency.Version = referencePom.Version;
					}
				}
				else
				{
					if (referencePom != null)
					{
						pom.Dependencies.Add(new ProjectDependency()
						{
							Name = referencePom.Name,
							Version = referencePom.Version
						});
					}
					else if (!onlyAddCrudeDependencies)
					{
						pom.Dependencies.Add(new ProjectDependency()
						{
							Name = reference.Name,
							Version = "*"
						});
					}
				}
			}


		}

		private static string GetAssemblyName(string fullname)
		{
			string name;

			try
			{
				name = AssemblyName.GetAssemblyName(fullname).Name;
			}
			catch
			{
				if (!fullname.Contains(","))
				{
					name = fullname;
				}
				else
				{
					name = fullname.Substring(0, fullname.IndexOf(","));
				}
			}

			return name;
		}

		private static void ResolveAssemblyReferences(Project project, ProjectObjectModel pom,
			 bool onlyAddCrudeDependencies, bool resolveToRemoteRepositoryVersion)
		{
			IList<AssemblyReference> references = project.GetReferences();

			IRemoteRepository remoteRepositoy = new RemoteRepositoryBuilder().Build();

			foreach (AssemblyReference reference in references)
			{
				string assemblyName = GetAssemblyName(reference.Name);

				ProjectDependency existingProjectDependency =
						(from dependency in pom.Dependencies
						 where dependency.Name == assemblyName
						 select dependency).FirstOrDefault();

				if (existingProjectDependency != null)
				{
					if (resolveToRemoteRepositoryVersion)
					{
						string oldVersion = existingProjectDependency.Version;

						existingProjectDependency.Version = "*";

						ProjectObjectModel referencedPom =
								remoteRepositoy.GetProjectObjectModel(existingProjectDependency);

						if (referencedPom != null)
						{
							existingProjectDependency.Version = referencedPom.Version;
						}
						else
						{
							existingProjectDependency.Version = oldVersion;
						}
					}
				}
				else
				{
					string hintPath = reference.HintPath;

					if (string.IsNullOrEmpty(hintPath) || !File.Exists(hintPath))
					{
						hintPath = string.Format("{0}\\bin\\debug\\{1}.dll", project.ProjectDirectory, assemblyName);

						if (!File.Exists(hintPath))
						{
							hintPath = null;
						}
					}

					string version = "*";

					if (!string.IsNullOrEmpty(hintPath))
					{
						try
						{
							Assembly assembly = Assembly.ReflectionOnlyLoadFrom(hintPath);
							if (assembly != null)
							{
								version = assembly.GetName().Version.ToString();
							}
						}
						catch
						{
						}
					}

					ProjectDependency newProjectDependency = new ProjectDependency
					{
						Name = assemblyName,
						Version = version
					};


					ProjectObjectModel referencePom = null;
					if (onlyAddCrudeDependencies || resolveToRemoteRepositoryVersion)
					{
						referencePom =
										remoteRepositoy.GetProjectObjectModel(newProjectDependency);

						if (resolveToRemoteRepositoryVersion && referencePom != null)
						{
							newProjectDependency.Version = referencePom.Version;
						}
					}

					if (onlyAddCrudeDependencies)
					{
						if (referencePom != null)
						{
							pom.Dependencies.Add(newProjectDependency);
						}
					}
					else
					{
						pom.Dependencies.Add(newProjectDependency);
					}
				}
			}
		}
	}
}
